package nl.ceasaro.poker.stats.mvc;

import nl.ceasaro.jeubelix.mvc.AbstractJeubelixController;
import nl.ceasaro.playingcards.Card;
import nl.ceasaro.playingcards.CardContainer;
import nl.ceasaro.poker.ranking.PokerHand;
import nl.ceasaro.poker.ranking.PokerRanker;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import umontreal.iro.lecuyer.probdist.HypergeometricDist;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Set;

/**
 * Created by Zerg.
 * User: cees
 * Date: May 28, 2007
 * Time: 11:35:50 PM
 *
 * @todo Document this class
 */
public class StatisticsController extends AbstractJeubelixController
{
   private static final Log log = LogFactory.getLog(StatisticsController.class);
   private CardContainer pokerCards;
   private PokerRanker pokerRanker;

   /**
    * @param httpServletRequest the HTTP request
    * @return a {@link nl.ceasaro.poker.texasHoldEm.game.TexasTable} command object.
    * @throws Exception
    */
   protected Object formBackingObject(HttpServletRequest httpServletRequest) throws Exception
   {
      return PokerStatsCmd.class;
   }

   protected ModelAndView showJeubelixForm(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BindException bindException) throws Exception
   {
      log.debug("in showJeubelixForm");


      int turns = 1000;
      String turnsString = httpServletRequest.getParameter("turns");
      if (turnsString != null)
      {
         try
         {
            turns = Integer.parseInt(turnsString);
         }
         catch (NumberFormatException e)
         {
            log.warn(e, e);
         }
      }
      int cardsToDeal = 7;
      String cardsToDealString = httpServletRequest.getParameter("cards-to-deal");
      if (cardsToDealString != null)
      {
         try
         {
            cardsToDeal = Integer.parseInt(cardsToDealString);
         }
         catch (NumberFormatException e)
         {
            log.warn(e, e);
         }
      }
      RankCounter[] rankCounter = getStats(turns, cardsToDeal);
      addMathematicalChange(rankCounter, cardsToDeal);
      PokerStatsResult pokerStatsResult = new PokerStatsResult();
      pokerStatsResult.setTurns(turns);
      pokerStatsResult.setCardsDealed(cardsToDeal);
      pokerStatsResult.setRankCounter(rankCounter);
      ModelAndView view = new ModelAndView("poker.stats");
      view.addObject(pokerStatsResult);
      return view;
   }

   private void addMathematicalChange(RankCounter[] rankCounter, int draws)
   {
      //m=D: correct possible cards in stack.
      //l=N: all cards in stack
      //k=x: needed cards from stack.
      //x=n: drawn cards (tries)

      int totalCardsInDeck = 52;
      for (RankCounter counter : rankCounter)
      {
         double probability = 0;
         if (counter.getRank() == PokerRanker.PokerRank.ROYAL_FLUSCH)
         {

         }
         if (counter.getRank() == PokerRanker.PokerRank.FOUR_OF_A_KIND)
         {
            double probabilityOfOneQuad = cardDrawProbabilty(4, totalCardsInDeck, draws, 4);
            counter.setProbability(13 * probabilityOfOneQuad);
         }
         if (counter.getRank() == PokerRanker.PokerRank.FULL_HOUSE)
         {
         }
         if (counter.getRank() == PokerRanker.PokerRank.FLUSH)
         {
            double probabilityOfFlushOfOneSuit = cardDrawProbabilty(13, totalCardsInDeck, draws, 5);
            counter.setProbability(4 * probabilityOfFlushOfOneSuit);
         }
         if (counter.getRank() == PokerRanker.PokerRank.STRAIGHT)
         {
         }
         if (counter.getRank() == PokerRanker.PokerRank.THREE_OF_A_KIND)
         {
            double probabilityOfOneTriple = cardDrawProbabilty(4, totalCardsInDeck, draws, 3);
            counter.setProbability(13 * probabilityOfOneTriple);
         }
         if (counter.getRank() == PokerRanker.PokerRank.TWO_PAIR)
         {
         }
         if (counter.getRank() == PokerRanker.PokerRank.PAIR)
         {
            double probabilityOfOnePair = cardDrawProbabilty(4, totalCardsInDeck, draws, 2);
            counter.setProbability(13 * probabilityOfOnePair);
         }
      }
   }

   protected RankCounter[] getStats(int turns, int cardsToDeal)
   {
      RankCounter[] counter = {new RankCounter(PokerRanker.PokerRank.ROYAL_FLUSCH, 0),
              new RankCounter(PokerRanker.PokerRank.STRAIGHT_FLUSH, 0),
              new RankCounter(PokerRanker.PokerRank.FOUR_OF_A_KIND, 0),
              new RankCounter(PokerRanker.PokerRank.FULL_HOUSE, 0),
              new RankCounter(PokerRanker.PokerRank.FLUSH, 0),
              new RankCounter(PokerRanker.PokerRank.STRAIGHT, 0),
              new RankCounter(PokerRanker.PokerRank.THREE_OF_A_KIND, 0),
              new RankCounter(PokerRanker.PokerRank.TWO_PAIR, 0),
              new RankCounter(PokerRanker.PokerRank.PAIR, 0),
              new RankCounter(PokerRanker.PokerRank.HIGH_CARD, 0)
      }; // set all counter to zero
      for (int i = 0; i < turns; i++)
      {
         pokerCards.reset();
         pokerCards.shuffle();
         Set<Card> cardSet = pokerCards.getCards(cardsToDeal);
         ArrayList<Card> cards = new ArrayList<Card>(cardSet);
         PokerHand pokerHand = pokerRanker.getHighestRank(cards);
         for (RankCounter rankCounter : counter)
         {
            if (rankCounter.getRank() == pokerHand.getRank())
            {
               rankCounter.turf();
            }
         }
      }
      for (RankCounter rankCounter : counter)
      {
         rankCounter.setPercentage((int) (((double) rankCounter.getCount() / (double) turns) * 100));
      }
      return counter;
   }

   protected ModelAndView processJeubelixForm(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object, BindException bindException) throws Exception
   {
      return null;
   }


   public CardContainer getPokerCards()
   {
      return pokerCards;
   }

   public void setPokerCards(CardContainer pokerCards)
   {
      this.pokerCards = pokerCards;
   }

   public PokerRanker getPokerRanker()
   {
      return pokerRanker;
   }

   public void setPokerRanker(PokerRanker pokerRanker)
   {
      this.pokerRanker = pokerRanker;
   }

   /**
    * @param cardsAvailableOfOneKind e.g. 4 if you want two's or jack's. But 13 if you want spades, diamonds.
    * @param totalCardsAvailable     most of the time 52 cards of one deck
    * @param draws                   The amount of card draws in a texas hold'em game this is 7.
    * @param cardsNeededOfOneKind    For a three-of-a-kind 3, for a flush 5.
    * @return the change of hitting the desired cards.
    */
   public static double cardDrawProbabilty(int cardsAvailableOfOneKind,
                                           int totalCardsAvailable,
                                           int draws,
                                           int cardsNeededOfOneKind)
   {
      return HypergeometricDist.prob(cardsAvailableOfOneKind, totalCardsAvailable, draws, cardsNeededOfOneKind);
   }

}
